﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using Kyoh.Lib.Component;
using Kyoh.Lib.Component.Globalization;
using System.Text;
using System.Collections.Specialized;
using System.Data;

namespace Kyoh.Lib.WinForms
{
    /// <summary><see cref="DataGridView"/> に対し、バインドされたデータの属性に従い列の表示スタイルを設定するクラスを定義します。</summary>
    public class DataGridViewColumnDesignBinder : System.ComponentModel.Component
    {
        private DataGridView _dataGridView;
        /// <summary>設定対象の <see cref="DataGridView"/> を取得・設定します。</summary>
        public DataGridView DataGridView
        {
            get { return _dataGridView; }
            set
            {
                if (_dataGridView == value)
                    return;

                if (_dataGridView != null)
                {
                    _dataGridView.ColumnDataPropertyNameChanged -= OnColumnDataPropertyNameChanged;
                    _dataGridView.BindingContextChanged -= OnBindingContextChanged;
                    _dataGridView.DataSourceChanged -= OnDataSourceChanged;
                    _dataGridView.DataMemberChanged -= OnDataMemberChanged;
                    _dataGridView.AutoGenerateColumnsChanged -= OnAutoGenerateColumnsChanged;
                }

                if (value != null)
                {
                    value.ColumnDataPropertyNameChanged += OnColumnDataPropertyNameChanged;
                    value.BindingContextChanged += OnBindingContextChanged;
                    value.DataSourceChanged += OnDataSourceChanged;
                    value.DataMemberChanged += OnDataMemberChanged;
                    value.AutoGenerateColumnsChanged += OnAutoGenerateColumnsChanged;
                    value.CellContextMenuStripNeeded += OnCellContextMenuStripNeeded;
                }

                _dataGridView = value;

                if (value != null)
                    FixColumns();
            }
        }

        private void OnCellContextMenuStripNeeded(object sender, DataGridViewCellContextMenuStripNeededEventArgs e)
        {
            if (e.RowIndex < 0)
                e.ContextMenuStrip = BuildVisibilityMenu();
        }

        /// <summary><see cref="DataGridViewColumnDesignBinder"/> クラスの新しいインスタンスを初期化します。</summary>
        public DataGridViewColumnDesignBinder()
        {
            VisibilityMenuCaption = WinForms.Properties.Resources.DataGridViewColumnDesignBinderVisibilityMenuCaption;
        }

        /// <summary>使用しているアンマネージ リソースを解放し、オプションでマネージ リソースも解放します。</summary>
        /// <param name="disposing">
        /// マネージ リソースとアンマネージ リソースの両方を解放する場合は <c>true</c>。
        /// アンマネージ リソースだけを解放する場合は <c>false</c>。
        /// </param>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
                _hiddenMenu.Dispose();
            base.Dispose(disposing);
        }

        #region Disable/Invisible Columns

        private readonly StringCollection _invisibleColumns = new StringCollection();
        /// <summary>非表示の列を取得・設定します。</summary>
        [DefaultValue(null)]
        public string InvisibleColumns
        {
            get { return JoinString(_invisibleColumns); }
            set
            {
                SplitString(_invisibleColumns, value);
                FixColumns();
            }
        }

        private string JoinString(StringCollection collection)
        {
            if (collection.Count > 0)
            {
                var output = new StringBuilder();
                foreach (var item in collection)
                    output.Append(item).Append(", ");
                output.Length -= 2;
                return output.ToString();
            }
            return null;
        }

        private void SplitString(StringCollection collection, string value)
        {
            collection.Clear();
            if (value != null)
            {
                value = value.Replace(" ", string.Empty);
                if (!string.IsNullOrEmpty(value))
                    collection.AddRange(value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
            }
        }

        /// <summary>非表示の列と表示中の列を設定するコンテキストメニューを表示するかどうかを取得・設定します。</summary>
        [Browsable(true)]
        [EditorBrowsable]
        [DefaultValue(false)]
        public bool VisibilityMenu { get; set; }
        /// <summary>非表示の列と表示中の列を設定するコンテキストメニューの項目を、メニュー無いのサブメニューとしてグループ化するかどうかを取得・設定します。</summary>
        [Browsable(true)]
        [EditorBrowsable]
        [DefaultValue(false)]
        public bool VisibilityMenuAsSubMenu { get; set; }
        /// <summary>非表示の列と表示中の列を設定するコンテキストメニューのキャプション文字列を取得・設定します。</summary>
        [Browsable(true)]
        [EditorBrowsable]
        [Localizable(true)]
        [DefaultValueFromResource(typeof(Properties.Resources), "DataGridViewColumnDesignBinderVisibilityMenuCaption")]
        public string VisibilityMenuCaption { get; set; }

        private readonly ContextMenuStrip _hiddenMenu = new ContextMenuStrip();

        private ContextMenuStrip BuildVisibilityMenu()
        {
            if (_dataGridView == null)
                return null;

            var menu = ContextMenuStrip ?? _hiddenMenu;
            for (int i = 0; i < menu.Items.Count;)
            {
                if (menu.Items[i] is ColumnVisibilityContextMenuItem)
                    menu.Items[i].Dispose();
                else
                    i++;
            }

            ToolStripItemCollection items;
            if (VisibilityMenuAsSubMenu)
            {
                var menuItem =
                    new ColumnVisibilityContextMenuItem
                        {
                            Text =
                                string.IsNullOrEmpty(VisibilityMenuCaption)
                                    ? WinForms.Properties.Resources.
                                          DataGridViewColumnDesignBinderVisibilityMenuCaption
                                    : VisibilityMenuCaption
                        };
                menu.Items.Add(menuItem);
                items = menuItem.DropDownItems;
            }
            else
                items = menu.Items;

            foreach (DataGridViewColumn col in _dataGridView.Columns)
            {
                var itemProperty = GetColumnName(col);
                if (!IsNullOrWhiteSpace(itemProperty))
                    items.Add(new ColumnVisibilityContextMenuItem(this, col));
            }
            return menu;
        }

        private static string GetColumnName(DataGridViewColumn col)
        {
            return IsNullOrWhiteSpace(col.HeaderText) ? col.DataPropertyName : col.HeaderText;
        }

        private static bool IsNullOrWhiteSpace(string str)
        {
            if (string.IsNullOrEmpty(str))
                return true;
            else
                return string.IsNullOrEmpty(str.Trim());
        }

        /// <summary>非表示の列と表示中の列を設定するコンテキストメニューを取得・設定します。</summary>
        [DefaultValue(null)]
        public ContextMenuStrip ContextMenuStrip { get; set; }

        private sealed class ColumnVisibilityContextMenuItem : ToolStripMenuItem
        {
            public ColumnVisibilityContextMenuItem()
            {
            }

            public ColumnVisibilityContextMenuItem(DataGridViewColumnDesignBinder binder, DataGridViewColumn column)
            {
                CheckOnClick = true;
                Text = column.HeaderText;
                Checked = column.Visible;
                _binder = binder;
                _column = column;
            }

            private readonly DataGridViewColumn _column;
            private readonly DataGridViewColumnDesignBinder _binder;

            protected override void OnCheckedChanged(EventArgs e)
            {
                if (_column != null)
                {
                    var name = GetColumnName(_column);
                    if (!IsNullOrWhiteSpace(name))
                    {
                        _column.Visible = Checked;
                        if (Checked)
                            _binder._invisibleColumns.Add(name);
                        else if (_binder._invisibleColumns.Contains(name))
                            _binder._invisibleColumns.Remove(name);
                    }
                }
                base.OnCheckedChanged(e);
            }
        }

        #endregion

        #region FixColumns

        private PropertyDescriptorCollection Properties
        {
            get
            {
                return (_dataGridView != null && _dataGridView.DataSource != null && _dataGridView.BindingContext != null)
                    ? _dataGridView.BindingContext[_dataGridView.DataSource, _dataGridView.DataMember ?? string.Empty].GetItemProperties()
                    : null;
            }
        }

        private void FixColumns()
        {
            var properties = Properties;
            if (_dataGridView == null)
                return;
            if (properties == null)
                return;

            foreach (DataGridViewColumn column in _dataGridView.Columns)
                FixColumn(column, properties);
        }

        private void FixColumn(DataGridViewColumn column, PropertyDescriptorCollection properties)
        {
            if (_dataGridView == null)
                return;
            if (properties == null)
                return;

            if (column.IsDataBound && !string.IsNullOrEmpty(column.DataPropertyName))
            {
                if (_invisibleColumns.Contains(column.DataPropertyName))
                    column.Visible = false;
                else
                    foreach (PropertyDescriptor property in properties)
                        if (SetColumnAttribute(column, property))
                            break;
            }
        }

        private bool SetColumnAttribute(DataGridViewColumn column, PropertyDescriptor property)
        {
            if (property.Name != column.DataPropertyName)
                return false;
            if (property.IsBrowsable)
            {
                if (property.ComponentType == typeof(DataRowView) || property.ComponentType.IsSubclassOf(typeof(DataRowView)))
                {
                }
                SetColumnSize(
                    column,
                    GetAttr<DisplaySizeAttribute>(property),
                    GetAttr<DisplayAutoSizeAttribute>(property));

                SetColumnFont(
                    column,
                    GetAttr<DisplayFontFamilyAttribute>(property),
                    GetAttr<DisplayFontSizeAttribute>(property));
            }
            else
                column.Visible = false;
            return true;
        }

        private static TAttribute GetAttr<TAttribute>(PropertyDescriptor property)
            where TAttribute : Attribute
        {
            return property.Attributes[typeof(TAttribute)] as TAttribute;
        }

        private static void SetColumnSize(DataGridViewColumn column, DisplaySizeAttribute size, DisplayAutoSizeAttribute autoSize)
        {
            // DisplaySizeAttribute
            if (size != null)
                column.Width = size.Size;

            // DisplayAutoSizeAttribute
            if (autoSize != null)
            {
                column.AutoSizeMode = (DataGridViewAutoSizeColumnMode)(int)autoSize.AutoSizeMode;
                column.FillWeight = autoSize.FillWeight;
            }
        }

        private static void SetColumnFont(DataGridViewColumn column, DisplayFontFamilyAttribute family, DisplayFontSizeAttribute fontSize)
        {
            //DisplayFontSizeAttribute
            float size = 0f;
            Font defaultFont = null;

            if (column.DefaultCellStyle != null
                && column.DefaultCellStyle.Font != null)
            {
                defaultFont = column.DefaultCellStyle.Font;
                size = defaultFont.Size;
            }
            else if (column.DataGridView != null)
            {
                defaultFont = column.DataGridView.Font;
                size = defaultFont.Size;
            }

            if (fontSize != null)
                size = fontSize.Size;

            //DisplayFontFamilyAttribute
            if (family != null && !Equals(size, 0f))
            {
                switch (family.Family)
                {
                    case DisplayFontFamilyAttribute.Monospace:
                        if (column.DefaultCellStyle != null)
                            column.DefaultCellStyle.Font = new Font(FontFamily.GenericMonospace, size);
                        break;
                    case DisplayFontFamilyAttribute.SansSerif:
                        if (column.DefaultCellStyle != null)
                            column.DefaultCellStyle.Font = new Font(FontFamily.GenericSansSerif, size);
                        break;
                    case DisplayFontFamilyAttribute.Serif:
                        if (column.DefaultCellStyle != null)
                            column.DefaultCellStyle.Font = new Font(FontFamily.GenericSerif, size);
                        break;
                    default:
                        if (column.DefaultCellStyle != null)
                            column.DefaultCellStyle.Font = new Font(family.Family, size);
                        break;
                }
            }
            else if (!Equals(size, 0f))
                if (column.DefaultCellStyle != null && defaultFont != null)
                    column.DefaultCellStyle.Font = new Font(defaultFont.FontFamily, size);
        }

        #endregion

        private void OnColumnDataPropertyNameChanged(object sender, DataGridViewColumnEventArgs e)
        {
            FixColumn(e.Column, Properties);
        }

        private void OnBindingContextChanged(object sender, EventArgs e)
        {
            FixColumns();
        }

        private void OnDataSourceChanged(object sender, EventArgs e)
        {
            FixColumns();
        }

        private void OnDataMemberChanged(object sender, EventArgs e)
        {
            FixColumns();
        }

        private void OnAutoGenerateColumnsChanged(object sender, EventArgs e)
        {
            if (_dataGridView != null && _dataGridView.AutoGenerateColumns)
                FixColumns();
        }
    }
}
